
#pragma once
// #include <gtest/gtest.h>
#include <any>
#include <cstddef>
// #include <entt/entt.hpp>
#include <functional>
#include <queue>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeindex>
#include <unordered_map>

#include "./plugin.hpp"
#include "./scheduler.hpp"
#include "./stage.hpp"
#include "./utils.hpp"
#include "./world.hpp"

namespace qbe::ecs {

class App : public World {
 public:
  using s_ptr = std::shared_ptr<App>;
  App(){};
  ~App(){};

  template <typename... Args>
  static s_ptr create(Args&&... args) {
    return std::make_shared<App>(std::forward<Args>(args)...);
  }

 private:
  Scheduler _scheduler;
  std::vector<std::shared_ptr<Plugin>> _plugins;

  template <typename GivenPlugin>
  void add_plugin(const GivenPlugin& plugin) {
    static_assert(std::is_base_of_v<Plugin, GivenPlugin>, "Given plugin does not derive from Plugin class");
    static_assert(std::is_move_constructible_v<GivenPlugin>, "Given plugin must be move constructible");
    _plugins.push_back(std::make_unique<GivenPlugin>(plugin));

    _plugins.back()->build(*this);
  }

 public:
  template <typename GivenPlugin, typename... Arg>
  void emplace_plugin(Arg... arg) {
    static_assert(std::is_base_of_v<Plugin, GivenPlugin>, "Given plugin does not derive from Plugin class");
    static_assert(std::is_move_constructible_v<GivenPlugin>, "Given plugin must be move constructible");
    _plugins.push_back(std::make_unique<GivenPlugin>(arg...));

    _plugins.back()->build(*this);
  }

  template <typename... GivenPlugin>
  void add_plugins(const GivenPlugin&... plugins) {
    ((add_plugin(std::forward<const GivenPlugin&>(plugins))), ...);
  }

  template <class... System>
  void add_systems(System&... system) {
    (_scheduler.add_system(system), ...);
  }

  template <class Stage, class... System>
  void add_systems(System&... system) {
    (_scheduler.add_system<Stage>(system), ...);
  }

  template <class F, class S, class... Args>
  void add_class_system(const F& func) {
    _scheduler.add_class_system<F, S, Args...>(func);
  }

  template <typename Stage>
  void add_stage() {
    _scheduler.insert_schedule<Stage>();
  }

  void run_setup() { _scheduler.run_setup(*this); }
  void run() { _scheduler.run(*this); }
};

}  // namespace qbe::ecs
